package test;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.content.res.AXmlResourceParser;

public class AXMLPrinter {
	private static final float[] RADIX_MULTS = { 0.0039063F, 3.051758E-005F, 1.192093E-007F, 4.656613E-010F };

	private static final String[] DIMENSION_UNITS = { "px", "dip", "sp", "pt", "in", "mm", "", "" };

	private static final String[] FRACTION_UNITS = { "%", "%p", "", "", "", "", "", "" };

	public static void main(String[] arg) {
		arg = new String[] { "AndroidManifest.xml" };
		if (arg.length < 1) {
			log("Usage: AXMLPrinter <binary xml file>", new Object[0]);
			return;
		}
		XmlPullParser androidManifest = getAndroidManifest(arg[0]);
		String apkName = getApkName(androidManifest);
		System.out.println(apkName);

	}

	private static String getApkName(XmlPullParser parser) {
		if (parser == null) {
			return null;
		}
		try {
			AppInfo appInfo = new AppInfo();
//			String namespace = "http://schemas.android.com/apk/res/android";
			String namespace = "";
			for (int type = parser.getEventType(); type != XmlPullParser.END_DOCUMENT; type = parser.next()) {
				if (type == XmlPullParser.START_TAG) {
					String name = parser.getName();
					if ("manifest".equals(name)) {
						/*
						 * <manifest xmlns:android="http://schemas.android.com/apk/res/android"
						 * android:versionCode="1086" android:versionName="1.086"
						 * android:installLocation="0" android:compileSdkVersion="23"
						 * android:compileSdkVersionCodename="6.0-2438415" package="com.lwhy.dbxgs"
						 * platformBuildVersionCode="1086" platformBuildVersionName="1.086" >
						 */
						int attributeCount = parser.getAttributeCount();
						for (int i = 0; i < attributeCount; i++) {
							String attributeName = parser.getAttributeName(i);
							String attributeNamespace = parser.getAttributeNamespace(i);
							if (!isEmpty(attributeNamespace)) {
								namespace = attributeNamespace;
							}
							String attributePrefix = parser.getAttributePrefix(i);
							String attributeType = parser.getAttributeType(i);
							String attributeValue = parser.getAttributeValue(i);
							String a = parser.getAttributeValue(attributeNamespace, attributeName);

							if ("android:versionCode".equals(attributeName)) {
								appInfo.versionCode = Integer.valueOf(attributeValue);
							} else if ("android:versionName".equals(attributeName)) {
								appInfo.versionName = attributeValue;
							} else if ("installLocation".equals(attributeName)) {
							} else if ("compileSdkVersion".equals(attributeName)) {
							} else if ("compileSdkVersionCodename".equals(attributeName)) {
							} else if ("package".equals(attributeName)) {
								appInfo.packageName = attributeValue;
							} else if ("platformBuildVersionCode".equals(attributeName)) {
							} else if ("platformBuildVersionName".equals(attributeName)) {
							}
						}

					} else if ("uses-sdk".equals(name)) {
						// <uses-sdk android:minSdkVersion="21"
						// android:targetSdkVersion="28"></uses-feature>
						String attributeValue1 = parser.getAttributeValue(namespace, "minSdkVersion");
						String attributeValue2 = parser.getAttributeValue(namespace, "targetSdkVersion");
						if (!isEmpty(attributeValue1)) {
							appInfo.minSdkVersion = attributeValue1;
						}
						if (!isEmpty(attributeValue2)) {
							appInfo.targetSdkVersion = attributeValue2;
						}
					} else if ("uses-feature".equals(name)) {
						// <uses-feature android:glEsVersion="0x00020000"></uses-feature>
					} else if ("uses-permission".equals(name)) {
						// <uses-permission
						// android:name="android.permission.INTERNET"></uses-permission>
						appInfo.requestedPermissionInfo.add(parser.getAttributeValue(namespace, "name"));
					} else if ("permission".equals(name)) {
						// <permission android:name="getui.permission.GetuiService.com.lwhy.dbxgs"
						// android:protectionLevel="0x00000000"></permission>
					} else if ("application".equals(name)) {
						/*
						 * <application android:theme="@style/Theme.MyApplication"
						 * android:label="@7F0E003D" android:icon="@7F0C0000"
						 * android:name="cn.dxs.myapplication.App" android:exported="true"
						 * android:allowBackup="true" android:supportsRtl="true"
						 * android:roundIcon="@mipmap/ic_launcher_round"
						 * android:usesCleartextTraffic="true" android:networkSecurityConfig="@7F11000C"
						 * android:appComponentFactory="android.support.v4.app.CoreComponentFactory" >
						 */

						int attributeCount = parser.getAttributeCount();
						for (int i = 0; i < attributeCount; i++) {
							String attributeName = parser.getAttributeName(i);
							String attributeNamespace = parser.getAttributeNamespace(i);
							String attributePrefix = parser.getAttributePrefix(i);
							String attributeType = parser.getAttributeType(i);
							String attributeValue = parser.getAttributeValue(i);
							String a = parser.getAttributeValue(attributeNamespace, attributeName);

							if ("theme".equals(attributeName)) {
							} else if ("android:label".equals(attributeName)) {
								appInfo.appName = attributeValue;
							} else if ("icon".equals(attributeName)) {
							} else if ("android:name".equals(attributeName)) {
								appInfo.applicationClassName = attributeValue;
							} else if ("exported".equals(attributeName)) {
							} else if ("allowBackup".equals(attributeName)) {
							} else if ("supportsRtl".equals(attributeName)) {
							} else if ("roundIcon".equals(attributeName)) {
							} else if ("usesCleartextTraffic".equals(attributeName)) {
							} else if ("networkSecurityConfig".equals(attributeName)) {
							} else if ("appComponentFactory".equals(attributeName)) {
							}
						}

					} else if ("meta-data".equals(name)) {
						// <meta-data android:name="android.app.lib_name" android:value="cocos2djs"/>
						int attributeCount = parser.getAttributeCount();
						if (attributeCount >= 2) {
							String attributeValue1 = parser.getAttributeValue(namespace, "android:name");
							String attributeValue2 = parser.getAttributeValue(namespace, "android:value");
							if (!isEmpty(attributeValue1) && !isEmpty(attributeValue2)) {
								appInfo.metaData.put(attributeValue1, attributeValue2);
								if ("android.app.lib_name".equals(attributeValue1)
										&& "cocos2djs".equals(attributeValue2)) {
									appInfo.isCocos2djs = true;
								}
							}
						}
					} else if ("activity".equals(name)) {
						/*
						 * <activity android:configChanges="keyboardHidden|orientation|screenSize"
						 * android:name="com.lwhy.dbxgs.SplashActivity"
						 * android:screenOrientation="portrait"
						 * android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> <intent-filter>
						 * <action android:name="android.intent.action.MAIN"/> <category
						 * android:name="android.intent.category.LAUNCHER"/> </intent-filter>
						 * </activity> <activity android:launchMode="singleTask"
						 * android:name="com.tencent.tauth.AuthActivity" android:noHistory="true">
						 * <intent-filter> <action android:name="android.intent.action.VIEW"/> <category
						 * android:name="android.intent.category.DEFAULT"/> <category
						 * android:name="android.intent.category.BROWSABLE"/> <data
						 * android:scheme="tencent101833275"/> </intent-filter> <intent-filter> <action
						 * android:name="android.intent.action.VIEW"/> <category
						 * android:name="android.intent.category.DEFAULT"/> <category
						 * android:name="android.intent.category.BROWSABLE"/> <data
						 * android:scheme="tencent101885818"/> </intent-filter> </activity>
						 */

//						int attributeCount = parser.getAttributeCount();
//						for (int i = 0; i < attributeCount; i++) {
//							String attributeName = parser.getAttributeName(i);
//							String attributeNamespace = parser.getAttributeNamespace(i);
//							String attributePrefix = parser.getAttributePrefix(i);
//							String attributeType = parser.getAttributeType(i);
//							String attributeValue = parser.getAttributeValue(i);
//							String a = parser.getAttributeValue(attributeNamespace, attributeName);
//						}
						appInfo.activityInfo.add(parser.getAttributeValue("", "android:name"));
					} else if ("intent-filter".equals(name)) {
					} else if ("action".equals(name)) {
						if ("android.intent.action.MAIN".equals(parser.getAttributeValue("", "android:name"))) {
							appInfo.mainEntryClassName = appInfo.activityInfo.get(appInfo.activityInfo.size() - 1);
						}
					} else if ("category".equals(name)) {
					} else if ("provider".equals(name)) {
						/*
						 * <provider android:authorities="com.lwhy.dbxgs.TTFileProvider"
						 * android:exported="false" android:grantUriPermissions="true"
						 * android:name="com.bytedance.sdk.openadsdk.TTFileProvider"> <meta-data
						 * android:name="android.support.FILE_PROVIDER_PATHS"
						 * android:resource="@xml/anythink_bk_tt_file_path"/> </provider>
						 */
						appInfo.providerInfo.add(parser.getAttributeValue("", "android:name"));
					} else if ("receiver".equals(name)) {
						/*
						 * <receiver android:name="com.igexin.sdk.PushReceiver"> <intent-filter> <action
						 * android:name="android.intent.action.BOOT_COMPLETED"/> <action
						 * android:name="android.net.conn.CONNECTIVITY_CHANGE"/> <action
						 * android:name="android.intent.action.USER_PRESENT"/> <action
						 * android:name="android.intent.action.MEDIA_MOUNTED"/> <action
						 * android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action
						 * android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
						 * </intent-filter> </receiver>
						 */
						appInfo.receiverInfo.add(parser.getAttributeValue("", "android:name"));
					} else if ("service".equals(name)) {
						/*
						 * <service android:exported="false" android:name=
						 * "com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService"
						 * android:process=":downloader"> <intent-filter> <action
						 * android:name="com.ss.android.socialbase.downloader.remote"/> </intent-filter>
						 * </service>
						 */
						appInfo.serviceInfo.add(parser.getAttributeValue("", "android:name"));
					} else if ("uses-library".equals(name)) {
						// <uses-library android:name="org.apache.http.legacy"
						// android:required="false"/>
						int attributeCount = parser.getAttributeCount();
						if (attributeCount >= 2) {
							String attributeValue1 = parser.getAttributeValue(namespace, "name");
							String attributeValue2 = parser.getAttributeValue(namespace, "required");
							if (!isEmpty(attributeValue1) && !isEmpty(attributeValue2)) {
								appInfo.usesLibrary.put(attributeValue1, attributeValue2);
							}
						}
					}
				}
			}
			return appInfo.getApkName();
		} catch (XmlPullParserException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private static XmlPullParser getAndroidManifest(String apkPath) {
		if (apkPath == null || apkPath.isEmpty()) {
			log("Usage: AXMLPrinter <binary xml file>", new Object[0]);
			return null;
		}
		try {
			AXmlResourceParser parser = new AXmlResourceParser();
			parser.open(new FileInputStream(apkPath));
			StringBuilder indent = new StringBuilder(10);
			StringBuilder sb = new StringBuilder();
			String indentStep = "\t";
			int type = 0;
			while ((type = parser.next()) != 1) {
				switch (type) {
				case 0:
					sb.append(log("<?xml version=\"1.0\" encoding=\"utf-8\"?>", new Object[0]));
					break;
				case 1:
					break;
				case 2:
					sb.append(log("%s<%s%s",
							new Object[] { indent, getNamespacePrefix(parser.getPrefix()), parser.getName() }));
					indent.append(indentStep);

					int namespaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1);
					int namespaceCount = parser.getNamespaceCount(parser.getDepth());
					for (int i = namespaceCountBefore; i != namespaceCount; i++) {
						sb.append(log("%sxmlns:%s=\"%s\"",
								new Object[] { indent, parser.getNamespacePrefix(i), parser.getNamespaceUri(i) }));
					}

					for (int i = 0; i != parser.getAttributeCount(); i++) {
						sb.append(log("%s%s%s=\"%s\"",
								new Object[] { indent, getNamespacePrefix(parser.getAttributePrefix(i)),
										parser.getAttributeName(i), getAttributeValue(parser, i) }));
					}
					sb.append(log("%s>", new Object[] { indent }));
					break;
				case 3:
					indent.setLength(indent.length() - indentStep.length());
					sb.append(log("%s</%s%s>",
							new Object[] { indent, getNamespacePrefix(parser.getPrefix()), parser.getName() }));
					break;
				case 4:
					sb.append(log("%s%s", new Object[] { indent, parser.getText() }));
				}
			}
			XmlPullParserFactory pullParserFactory = XmlPullParserFactory.newInstance();
			XmlPullParser pullParser = pullParserFactory.newPullParser();
			pullParser.setInput(new ByteArrayInputStream(sb.toString().getBytes()), "UTF-8");
			return pullParser;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private static String getNamespacePrefix(String prefix) {
		if ((prefix == null) || (prefix.length() == 0)) {
			return "";
		}
		return prefix + ":";
	}

	private static String getAttributeValue(AXmlResourceParser parser, int index) {
		int type = parser.getAttributeValueType(index);
		int data = parser.getAttributeValueData(index);
		if (type == 3) {
			return parser.getAttributeValue(index);
		}
		if (type == 2) {
			return String.format("?%s%08X", new Object[] { getPackage(data), Integer.valueOf(data) });
		}
		if (type == 1) {
			return String.format("@%s%08X", new Object[] { getPackage(data), Integer.valueOf(data) });
		}
		if (type == 4) {
			return String.valueOf(Float.intBitsToFloat(data));
		}
		if (type == 17) {
			return String.format("0x%08X", new Object[] { Integer.valueOf(data) });
		}
		if (type == 18) {
			return data != 0 ? "true" : "false";
		}
		if (type == 5) {
			return Float.toString(complexToFloat(data)) + DIMENSION_UNITS[(data & 0xF)];
		}
		if (type == 6) {
			return Float.toString(complexToFloat(data)) + FRACTION_UNITS[(data & 0xF)];
		}
		if ((type >= 28) && (type <= 31)) {
			return String.format("#%08X", new Object[] { Integer.valueOf(data) });
		}
		if ((type >= 16) && (type <= 31)) {
			return String.valueOf(data);
		}
		return String.format("<0x%X, type 0x%02X>", new Object[] { Integer.valueOf(data), Integer.valueOf(type) });
	}

	private static String getPackage(int id) {
		if (id >>> 24 == 1) {
			return "android:";
		}
		return "";
	}

	private static String log(String format, Object[] arguments) {
		System.out.printf(format, arguments);
		System.out.println();
		return String.format(format + "\n", arguments);
	}

	public static float complexToFloat(int complex) {
		return (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4 & 0x3)];
	}

	private static boolean isEmpty(String str) {
		if (str == null || str.isEmpty()) {
			return true;
		}
		return false;
	}
}

class AppInfo {

	public String appName;// 名称
	public String packageName;// 包名
	public String versionName;// 版本号
	public int versionCode;// Version Code
	public String stub;// 加固类型

	public String minSdkVersion;// 最低兼容SDK版本
	public String targetSdkVersion;// 目标SDK版本
	public int UID;// UID
	public boolean isSystem;// 系统应用

	public String apkPath;// Apk路径
	public String apkSize;// Apk大小
	public String sig;// 签名
	public String MD5;// 签名MD5
	public String SHA1;// 签名SHA1
	public String SHA256;// 签名SHA256
	public String signatureOwner;// 签名所有者
	public String signaturePublisher;// 签名发布者
	public String signatureSerialNumber;// 签名序列号
	public String signatureAlgorithmName;// 签名算法名称
	public String signatureVersion;// 签名版本
	public String signatureExpiryDateStart;// 有效期开始日期
	public String signatureExpiryDateEnd;// 有效期截止日期
	public String nativePath;// Native库路径
	public String appDataDirectory;// 应用数据目录

	public String mainEntryClassName;// 主入口类
	public String applicationClassName;// Application类名
	public String mainCPUABI;// 主CPU Abi

	public String firstInstallTime;// 首次安装时间
	public String lastUpdateTime;// 最近升级时间

	public List<String> requestedPermissionInfo = new ArrayList<>();
	public List<String> activityInfo = new ArrayList<>();
	public List<String> serviceInfo = new ArrayList<>();
	public List<String> receiverInfo = new ArrayList<>();
	public List<String> providerInfo = new ArrayList<>();

	public boolean isCocos2djs = false;// 是否cocos2djs
	public Map<String, String> metaData = new HashMap<>();// meta-data数据集
	public Map<String, String> usesLibrary = new HashMap<>();// uses-library数据集
	
	public String getApkName() {
		StringBuilder sb = new StringBuilder();
		sb.append(appName).append("_");
		sb.append(packageName).append("_");
		sb.append(versionName).append("_");
		sb.append(versionCode);
		return sb.toString();
	}

}
